//=============================================================================
//
//   Copyright (C) 2020 All rights reserved.
//
//   Filename:		integratable.rs
//
//   Author: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Date: 2020-10-09 12:26
//
//   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Last modified:	2020-11-06 18:27
//
//   Description:
//
//=============================================================================
use crate::component::gauss_point::{GaussPoint, GaussPointLike};
use crate::dimension::DimType;
use crate::Real;
use std::ops::{AddAssign, Mul};

///
/// integratable
///
pub trait Integratable<DT: DimType> {
    fn get_gp_list(&self) -> &Vec<GaussPoint<DT>>;
    fn get_gp_list_mut(&mut self) -> &mut Vec<GaussPoint<DT>>;

    // -------------------------
    #[inline]
    fn clear_gp_list(&mut self) {
        self.get_gp_list_mut().clear();
    }
    #[inline]
    fn append_gauss_point(&mut self, rhs: GaussPoint<DT>) {
        if rhs.get_weight() > 1e-7 {
            self.get_gp_list_mut().push(rhs);
        }
    }
}

/// get integration of a integratable object
#[inline]
pub fn integrate<DT, Func, Res>(obj: &dyn Integratable<DT>, func: Func) -> Res
where
    DT: DimType,
    Func: Fn(&GaussPoint<DT>) -> Res,
    Res: Default + AddAssign<<Res as Mul<Real>>::Output> + Mul<Real>,
{
    let mut res = Res::default();
    for gp in obj.get_gp_list() {
        res += func(gp) * gp.get_weight();
    }
    res
}

/// get weighted average of a integratable object
#[inline]
pub fn weighted_average<DT, Func, Res>(
    obj: &dyn Integratable<DT>,
    func: Func,
) -> <Res as Mul<Real>>::Output
where
    DT: DimType,
    Func: Fn(&GaussPoint<DT>) -> Res,
    Res: Default + AddAssign<<Res as Mul<Real>>::Output> + Mul<Real>,
{
    integrate(obj, func) * get_weight(obj).recip()
}

/// get total weight of a integratable object
#[inline]
pub fn get_weight<DT>(obj: &dyn Integratable<DT>) -> Real
where
    DT: DimType,
{
    integrate(obj, |_| 1 as Real)
}

///
/// integratable
///
pub trait GaussPointDistributed {
    fn distribute_gauss_points(&mut self, gp_num: usize);
}

#[cfg(test)]
mod integrate_tests {
    use super::*;
    use crate::dimension::PlaneStress;
    use crate::geometry::point::PointLike;
    use std::vec::Vec;

    struct ShapeTest<DT: DimType> {
        gp_list: Vec<GaussPoint<DT>>,
    }
    integratable_impl!(ShapeTest<DT: DimType>);
    #[test]
    fn test_1() {
        let mut shp1 = ShapeTest::<PlaneStress> {
            gp_list: Vec::new(),
        };
        shp1.append_gauss_point(GaussPoint::<PlaneStress>::new(0f32, 0f32, 1f32));
        shp1.append_gauss_point(GaussPoint::<PlaneStress>::new(1f32, 0f32, 1f32));
        shp1.append_gauss_point(GaussPoint::<PlaneStress>::new(1f32, 1f32, 1f32));
        shp1.append_gauss_point(GaussPoint::<PlaneStress>::new(0f32, 1f32, 1f32));
        assert_eq!(get_weight(&shp1), 4f32);
        assert_eq!(
            integrate(&shp1, |gp| gp.as_np().x + gp.as_np().y),
            4f32
        );
        assert_eq!(integrate(&shp1, |gp| gp.as_np().x), 2f32);
        assert_eq!(weighted_average(&shp1, |_| 1 as Real), 1f32);
    }
}
